/**
 * 
 */
package fhq.hcmute.qrcode.common;

import fhq.hcmute.qrcode.encoder.BitArray;

/**
 * @author Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
public class BitMatrix {

	public static final int EMPTY = -1;
	public static final int TRUE = 1;
	public static final int FALSE = 0;

	public static final int[][] DETECTION_PATTERN = {
			{ TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE },
			{ TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE },
			{ TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE },
			{ TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE },
			{ TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE },
			{ TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, TRUE },
			{ TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE }, };
	public static final int[][] ADJUSTMENT_PATTERN = {
			{ TRUE, TRUE, TRUE, TRUE, TRUE },
			{ TRUE, FALSE, FALSE, FALSE, TRUE },
			{ TRUE, FALSE, TRUE, FALSE, TRUE },
			{ TRUE, FALSE, FALSE, FALSE, TRUE },
			{ TRUE, TRUE, TRUE, TRUE, TRUE } };

	private int[][] matrix;
	private int module;

	public BitMatrix(Version version) {
		this.module = version.getModules();
		this.matrix = new int[module][module];
		setDefaultMatrix();
		setDetectionPattern(0, 0);
		setDetectionPattern(0, module - 7);
		setDetectionPattern(module - 7, 0);

		int[] alignmentPattern = version.getAlignmentPatternPosition();
		for (int i = 0; i < alignmentPattern.length; i++) {
			for (int j = 0; j < alignmentPattern.length; j++) {
				int x = alignmentPattern[i];
				int y = alignmentPattern[j];
				if (matrix[x][y] == EMPTY) {
					setAdjustmentPattern(x, y);
				}
			}
		}
		setTiming();

		setHorizontalSeparator(0, 7);
		setHorizontalSeparator(0, module - 8);
		setHorizontalSeparator(module - 8, 7);

		setVerticalSeparator(7, 0);
		setVerticalSeparator(module - 8, 0);
		setVerticalSeparator(7, module - 8);
	}

	public BitMatrix(int dimensionX, int dimensionY) {
		this.matrix = new int[dimensionX][dimensionY];
		this.module = dimensionX;
	}

	public int get(int i, int j) {
		return this.matrix[i][j];
	}

	public void set(int i, int j, int value) {
		this.matrix[i][j] = value;
	}

	public boolean getBitAt(int i, int j) {
		return this.matrix[i][j] == TRUE ? true : false;
	}

	public void setVersionInformation(int[] versionInformation,
			MaskPattern maskPattern) {
		setVersionInformationVertical(0, module - 11, versionInformation);
		setVersionInformationHorizontal(module - 11, 0, versionInformation);
	}

	public void setFormatInformation(int[] formatInformation) {
		setFormatInformationVertical(formatInformation);
		setFormatInformationHorizontal(formatInformation);
	}

	private void normalization(int[] array) {
		for (int i = 0; i < array.length; i++) {
			if (array[i] == 1) {
				array[i] = TRUE;
			} else {
				array[i] = FALSE;
			}
		}
	}

	private void setFormatInformationVertical(int[] formatInformation) {
		normalization(formatInformation);
		matrix[module - 8][8] = TRUE;
		for (int i = 0; i < 15; i++) {
			if (i < 8) {
				if (matrix[i][8] == EMPTY) {
					matrix[i][8] = formatInformation[14 - i];
				} else {
					matrix[i + 1][8] = formatInformation[14 - i];
				}
			} else {
				if (matrix[module - 7 + i - 8][8] == EMPTY) {
					matrix[module - 7 + i - 8][8] = formatInformation[14 - i];
				} else {
					matrix[module - 7 + i - 8 + 1][8] = formatInformation[14 - i];
				}
			}
		}
	}

	private void setFormatInformationHorizontal(int[] formatInformation) {
		normalization(formatInformation);
		for (int i = 14; i >= 0; i--) {
			if (i > 7) {
				if (matrix[8][14 - i] == EMPTY) {
					matrix[8][14 - i] = formatInformation[14 - i];
				} else {
					matrix[8][14 - i + 1] = formatInformation[14 - i];
				}
			} else {
				if (matrix[8][module - i - 1] == EMPTY) {
					matrix[8][module - i - 1] = formatInformation[14 - i];
				} else {
					matrix[8][module - i] = formatInformation[14 - i];
				}
			}
		}
	}

	private void setVersionInformationVertical(int x, int y,
			int[] versionInformation) {
		normalization(versionInformation);
		int count = 0;
		for (int i = 0; i < 6; i++) {
			for (int j = 0; j < 3; j++) {
				matrix[x + i][y + j] = versionInformation[17 - count++];
			}
		}
	}

	private void setVersionInformationHorizontal(int x, int y,
			int[] versionInformation) {
		normalization(versionInformation);
		int count = 0;
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 6; j++) {
				matrix[x + i][y + j] = versionInformation[17 - count];
				count += 3;
			}
			count = i + 1;
		}
	}

	private void setHorizontalSeparator(int x, int y) {
		for (int i = 0; i < 8; i++) {
			matrix[x + i][y] = FALSE;
		}
	}

	private void setVerticalSeparator(int x, int y) {
		for (int i = 0; i < 8; i++) {
			matrix[x][i + y] = FALSE;
		}
	}

	private void setDefaultMatrix() {
		for (int i = 0; i < module; i++) {
			for (int j = 0; j < module; j++) {
				matrix[i][j] = EMPTY;
			}
		}
	}

	private void setTiming() {
		for (int i = 8; i < matrix.length - 8; i++) {
			if (i % 2 == 0) {
				// Vertical
				matrix[6][i] = TRUE;
				// Horizontal
				matrix[i][6] = TRUE;
			} else {
				matrix[6][i] = FALSE;
				matrix[i][6] = FALSE;
			}
		}
	}

	private void setAdjustmentPattern(int x, int y) {
		x -= 2;
		y -= 2;
		for (int i = 0; i < 5; i++) {
			for (int j = 0; j < 5; j++) {
				matrix[x + i][y + j] = ADJUSTMENT_PATTERN[i][j];
			}
		}
	}

	private void setDetectionPattern(int x, int y) {
		for (int i = 0; i < 7; i++) {
			for (int j = 0; j < 7; j++) {
				matrix[x + i][y + j] = DETECTION_PATTERN[i][j];
			}
		}
	}

	public int getModule() {
		return module;
	}

	public int[][] getRawValues() {
		return matrix;
	}

	public void appendDatas(BitArray bitArray, MaskPattern maskPattern) {
		int y = module - 1;
		int bitIndex = 0;
		int direction = -1;
		for (int x = module - 1; x > 0; x -= 2) {
			// Ignore the vertical timing.
			if (x == 6) {
				x -= 1;
			}
			while (y >= 0 && y < module) {
				for (int i = 0; i < 2; i++) {
					if (matrix[y][x - i] != EMPTY) {
						continue;
					}
					int value = FALSE;
					if (bitIndex < bitArray.getSize()) {
						value = bitArray.getBitAt(bitIndex) == true ? TRUE
								: FALSE;
						bitIndex++;
					} else {
						value = FALSE;
					}
					value = maskPattern.xor(y, x - i, value);
					matrix[y][x - i] = value;
				}
				y += direction;
			}
			direction = -direction;
			y += direction;
		}
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < module; i++) {
			for (int j = 0; j < module; j++) {
				if (getBitAt(i, j)) {
					builder.append(" X");
				} else {
					builder.append("  ");
				}
			}
			builder.append("\n");
		}
		return builder.toString();
	}
}
